﻿using System;
using System.Collections.Generic;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

using SmlEngine.Graphics;
using SmlEngine.Sprites.Base;
using SmlEngine.Sprites.Collections;
using SmlEngine.Sprites.Components;
using SmlEngine.Sprites.Settings;
using SmlEngine.UI.Input;

namespace SmlEngine.Sprites.Players
{
    public class Player : Npc
    {
        #region Constants

        private const float accelerationX = 800f;
        private const float maxSpeedX = 5000f;
        private const float runToWalkRatio = 2f;

        private const float groundFriction = 0.68f;
        private const float airFriction = 0.78f;

        private const float maxJumpTime = 1500f;
        private const float jumpLaunchSpeed = 500f;
        private const float gravity = 300f;

        private const float maxSpeedY = 3000f;
        private const float maxFallSpeed = 300f;
        private const float maxFloatSpeed = 300f;

        private const float jumpControlPower = 0.75f;

        #endregion

        #region Fields

        private float jumpTime;

        private bool isJumping;
        private bool wasJumping;

        private bool mode; //'Follow mouse' = true

        #endregion

        #region Properties

        //protected
        public virtual CollisionComponent CollisionComponent { get; set; }
        protected virtual InputComponent InputComponent { get; set; }
        protected virtual AnimationComponent AnimationComponent { get; set; }

        public PlayerState State { get; set; }
        
        #endregion

        public Player(Level owner)
            : base(owner)
        {
            CollisionComponent = new CollisionComponent(this);
            InputComponent = new InputComponent(this);
            AnimationComponent = new AnimationComponent(this);

            //Testing
            Texture2D graphics = PlayerSettings.Graphics;

            AnimationComponent.Animations.Add("Test", new Animation(graphics, 0.25f, 8, true));
            AnimationComponent.PlayAnimation("Test");

            Size = new Point(graphics.Width / 8, graphics.Height);
        }

        public override void Update(GameTime gameTime)
        {
            float delta = gameTime.GetElapsedSeconds();

            var current = InputComponent.Input.Current;

            if (InputComponent.Input.IsNewKeyPress(Keys.B))
                mode = !mode;

            if (mode)
            {
                //This makes the player follow the mouse. 

                Vector2 mouse = new Vector2(current.MouseState.X, current.MouseState.Y) + Owner.Camera.Position;

                Velocity = mouse - Position;
                Velocity.Normalize();
                Velocity *= 5;

                base.Update(gameTime);
            }
            else
            {

                float acceleration = 0f;

                if (current.IsKeyMapDown(InputSettings.Left))
                {
                    AnimationComponent.SpriteEffects = SpriteEffects.FlipHorizontally;
                    acceleration = -accelerationX;
                }
                else if (current.IsKeyMapDown(InputSettings.Right))
                {
                    AnimationComponent.SpriteEffects = SpriteEffects.None;
                    acceleration = accelerationX;
                }

                if (current.IsKeyMapDown(InputSettings.Run))
                {
                    acceleration *= runToWalkRatio;
                }

                isJumping = current.IsKeyMapDown(InputSettings.Jump);

                Acceleration = DoJump(delta, acceleration);

                Velocity = new Vector2(Velocity.X * (CollisionComponent.IsOnGround ? groundFriction : airFriction), Velocity.Y);

                base.Update(gameTime);

                //Apply gravity
                if (!CollisionComponent.IsOnGround)
                    Acceleration = new Vector2(Acceleration.X, Acceleration.Y + gravity);

                Clamp();

                if (Bounds.Bottom > Owner.Height)
                    Die();
            }
        }

        private Vector2 DoJump(float delta, float accelerationX)
        {
            //We invert accelerationY at the end, so invert current Acceleration here.
            float accelerationY = -Acceleration.Y;

            if (isJumping)
            {
                //Begin or continue jump
                if ((!wasJumping && CollisionComponent.IsOnGround) || jumpTime > 0f)
                {
                    if (jumpTime == 0f)
                    {
                        //Started a jump
                        //TODO: play sound or animation etc.
                    }

                    jumpTime += delta;
                }

                //If the player is ascending
                if (jumpTime > 0f && jumpTime <= maxJumpTime)
                {
                    accelerationY = jumpLaunchSpeed * (1f - (float)Math.Pow(jumpTime / maxJumpTime, jumpControlPower));
                }
                else
                {
                    //The player has reached the apex
                    jumpTime = 0f;
                }
            }
            else
            {
                //Cancel the jump
                jumpTime = 0f;
            }

            return new Vector2(accelerationX, -accelerationY);
        }

        private void Clamp()
        {
            Velocity = new Vector2(MathHelper.Clamp(Velocity.X, -maxSpeedX, maxSpeedX), 
                                   MathHelper.Clamp(Velocity.Y, -maxSpeedY, maxSpeedY));
        }

        public void Die()
        {
            State = PlayerState.Dead;
        }
    }
}
